﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;
using snat.algorithms.network.TFIDF;
using System.Threading.Tasks;
using System.IO;

namespace snat.algorithms.node.TFIDF {
    public class ForwardTFIDFNode<T> : TFIDFNode<T>, IAlgorithm where T : Document {

        private double threshold;
        public ForwardTFIDFNode(int _id, string _name, double threshold, int initialCluster = 0)
            : base(_id, _name, initialCluster) {
            this.threshold = threshold;
        }
        public ForwardTFIDFNode(int _id, string _name, int initialCluster = 0)
            : this(_id, _name, 0.25, initialCluster) { }


        public static int forwardCount = 0;
        public static int correct = 0;
        public static int wrong = 0;
        private static double sumValue = 0;
        private static int totalPredictions = 0;
        public static double AvgValue {
            get {
                return sumValue / (double)totalPredictions;
            }
        }

        //received -> sent
        Dictionary<Document, double> ForwardedDocuments = new Dictionary<Document, double>();

        Dictionary<Document, double> Predictions = new Dictionary<Document, double>();


        public override void OnMessageSend(Message m, BaseNode n) {
            Console.WriteLine(NetworkModel.Instance.MessageProcessedCount);
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                    //Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });
            SentDocuments.Add(document);

            if (ReceivedDocuments.Count > 0) {
                Parallel.ForEach<Term>(Terms.Values,
                    term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
                Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
                Parallel.ForEach<Document>(ReceivedDocuments,
                    d => { d.PopulateTFIDF(termsDict); });
                document.PopulateTFIDF(termsDict);

                //check for documents which may have been forwarded and take note
                foreach (Document d in ReceivedDocuments) {
                    double cs = document.CosineSimilarity(d);
                    if (cs > threshold) {
                        Console.WriteLine("FORWARDED!, Probability : {0}, ID : {1}", cs.ToString("F3"), document.ID);
                        if (!ForwardedDocuments.ContainsKey(document)) {
                            ForwardedDocuments.Add(document, cs);
                        } else {
                            ForwardedDocuments[document] = Math.Max(ForwardedDocuments[document], cs);
                        }
                        //take note of it for if anotehr message is received that is similar!!!
                        forwardCount++;
                    }
                }

                //check if any predictions were correct
                foreach (Document d in Predictions.Keys.ToList()) {
                    double cs = document.CosineSimilarity(d);
                    if (cs > threshold) {
                        Console.WriteLine("CORRECT!, Send: {0}, Forwarded: {1}, Jacard Index: {2}",
                            document.ID, document.ID, document.JaccardIndex(d));
                        correct++;
                        Predictions.Remove(d);
                        break;
                    }
                }
            }
        }

        public override void OnMessageReceive(Message m, BaseNode n) {
            bool updateTerms;
            lock (MessagesAddedToTerms) {
                if (!MessagesAddedToTerms.Contains(m.ID)) {
                    //first node to process
                    updateTerms = true;
                    MessagesAddedToTerms.Add(m.ID);
                   // Console.WriteLine("{0}, FIRST!!!!!!LOLROFL!?!!??##!", m.ID);
                } else {
                    //Console.WriteLine("{0}, second :(", m.ID);
                    //message already processed by other node, don't add its words to Terms
                    updateTerms = false;
                }
            }
            //create the document using dirty reflection
            T document = (T)Activator.CreateInstance(
                            typeof(T),
                            new object[] { m, Terms, updateTerms });
            ReceivedDocuments.Add(document);

            if (ForwardedDocuments.Count > 0) {
                Parallel.ForEach<Term>(Terms.Values,
                        term => { term.IDF = Math.Log((double)DocumentCount / (double)term.AppearenceCount); });
                Dictionary<Term, double> termsDict = Terms.Values.ToDictionary(e => e, e => e.IDF);
                Parallel.ForEach<Document>(ReceivedDocuments,
                    d => { d.PopulateTFIDF(termsDict); });
                document.PopulateTFIDF(termsDict);
            }

            //check if any documents like this have been forwarded previously, and predict if true
            foreach (Document d in ForwardedDocuments.Keys) {
                double cs = document.CosineSimilarity(d);
                if (cs > threshold) {
                    if (!Predictions.ContainsKey(document)) {
                        
                        
                        Predictions.Add(document, cs*ForwardedDocuments[d]);
                        
                    } else {
                        //forwardedDocuments[document] = Math.Pow(forwardedDocuments[document], cs);
                        Predictions[document] = Math.Max(Predictions[document], cs*ForwardedDocuments[d]);
                    }
                    totalPredictions++;
                    sumValue += Predictions[document];
                    Console.WriteLine("PREDICTION!, Probability : {0}, ID : {1}", 
                        Predictions[document].ToString("F3"), document.ID);
                }
            }
          //  Console.WriteLine("Node {0} now has {1} documents", m.Sender.ID, ReceivedDocuments.Count);
        }

        private static DateTime start = NetworkModel.Instance.CurrentTime;
        private static TimeSpan oneWeek = new TimeSpan(3, 0, 0, 0, 0);
        private static TimeSpan fourWeeks = new TimeSpan(28, 0, 0, 0, 0);
        public override void NetworkMessageEveryone(Message m) {
            ReceivedDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            SentDocuments.RemoveAll(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek);
            List<Document> toRemove = Predictions.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            foreach (Document d in toRemove) {
                Predictions.Remove(d);
                wrong++;
            }

            toRemove = ForwardedDocuments.Keys.Where(e => e.Date < NetworkModel.Instance.CurrentTime - oneWeek).ToList();
            foreach (Document d in toRemove) {
                ForwardedDocuments.Remove(d);
            }
        }

        private void ClearStuff() {
            Predictions.Clear();
        }

        private static StreamWriter output = new StreamWriter("../../../forwardPredict.txt");
        public override void NetworkMessageOnce(Message m) {
            /*if (start < NetworkModel.Instance.CurrentTime - fourWeeks) {
                start = NetworkModel.Instance.CurrentTime;
                Console.WriteLine("-------------------New month---------------------");
                double score = totalPredictions == 0 ? 0 : (double)correct / (double)totalPredictions;
                Console.WriteLine("Last time we got {0}% correct!, after making {1} predictions", score * 100, totalPredictions);
                output.WriteLine("Last time we got {0}% correct!, after making {1} predictions", score * 100, totalPredictions);
                correct = 0;
                totalPredictions = 0;
            }*/
        }

        public static void End() {
            Console.WriteLine("Predictions wrong: {0}, Predictions correct: {1}, Average prediction: {2}",
                wrong, correct, AvgValue);
            //output.WriteLine("There were {0} forwarded messages in the set of {1} messages", 
              //  forwardCount, NetworkModel.Instance.MessagesProcessed);
            output.Close();
        }
        private static bool registered = false;
        public override void Register() {
            base.Register();
            if (!registered) {
                registered = true;
                NetworkModel.Instance.TapeEnded += End;
                NetworkModel.Instance.TapePaused += End;
            }
        }

        public override void DeRegister() {
            base.DeRegister();
            if (registered) {
                registered = false;
                NetworkModel.Instance.TapeEnded -= End;
                NetworkModel.Instance.TapePaused -= End;
            }
        }

    }
}
